TOOL.Category = '(Malawar)'
TOOL.Name = '#Repulsor'
TOOL.Command = nil
TOOL.ConfigName = ''

TOOL.ClientConVar['keyup'] = '8'
TOOL.ClientConVar['keydown'] = '2'
TOOL.ClientConVar['rate'] = '100.0'

TOOL.ClientConVar['strength'] = '600'
TOOL.ClientConVar['damping'] = '100'

TOOL.ClientConVar['downonly'] = 1

TOOL.ClientConVar['distance'] = 200
TOOL.ClientConVar['forceinitial'] = 0

cleanup.Register('repulsors')

local repulsor_material = 'models/shadertest/shader4'

if ( CLIENT ) then
	language.Add( 'Tool_repulsor_name', 'Repulsor Spawner (Malawar)' )
	language.Add( 'Tool_repulsor_desc', 'Create Repulsors attached to any surface.' )
	language.Add( 'Tool_repulsor_0', 'Click somewhere to attach a Repulsor.' )

	language.Add( 'Undone_repulsor', 'Repulsor Undone' )
	language.Add( 'Cleanup_repulsors', 'Repulsor' )
	language.Add( 'Cleaned_repulsor', 'Cleaned up all repulsors' )
	language.Add( 'SBoxLimit_repulsors', 'Maximum Repulsors Reached' )
end

-- bitwise crap ganked from http://luaforge.net/projects/bit/
local function check_int(n)
 -- checking not float
 if(n - math.floor(n) > 0) then
  error("trying to use bitwise operation on non-integer!")
 end
end
local function to_bits(n)
 check_int(n)
 if(n < 0) then
  -- negative
  return to_bits(bit.bnot(math.abs(n)) + 1)
 end
 -- to bits table
 local tbl = {}
 local cnt = 1
 while (n > 0) do
  local last = math.fmod(n,2)
  if(last == 1) then
   tbl[cnt] = 1
  else
   tbl[cnt] = 0
  end
  n = (n-last)/2
  cnt = cnt + 1
 end
 return tbl
end
local function tbl_to_number(tbl)
 local n = table.getn(tbl)
 local rslt = 0
 local power = 1
 for i = 1, n do
  rslt = rslt + tbl[i]*power
  power = power*2
 end
 return rslt
end
local function expand(tbl_m, tbl_n)
 local big = {}
 local small = {}
 if(table.getn(tbl_m) > table.getn(tbl_n)) then
  big = tbl_m
  small = tbl_n
 else
  big = tbl_n
  small = tbl_m
 end
 -- expand small
 for i = table.getn(small) + 1, table.getn(big) do
  small[i] = 0
 end
end
local function bit_or(m, n)
 local tbl_m = to_bits(m)
 local tbl_n = to_bits(n)
 expand(tbl_m, tbl_n)
 local tbl = {}
 local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
 for i = 1, rslt do
  if(tbl_m[i]== 0 and tbl_n[i] == 0) then
   tbl[i] = 0
  else
   tbl[i] = 1
  end
 end
 return tbl_to_number(tbl)
end



local masks = {
  Water = 16432,
  Ground = 3,
  Props = 33636353 --81920
  }
	
for name, value in pairs(masks) do
	TOOL.ClientConVar[name] = 1
end
	
function TOOL:LeftClick( trace )
	if trace.Entity then
		if !trace.Entity:IsValid() or trace.Entity:IsPlayer() or trace.HitWorld or trace.Entity:IsNPC() then
			return false
		end
	end
	
	if(CLIENT) then
		return true
	end

	if(!SERVER) then return false end
	local ply = self:GetOwner()
	local Pos = trace.HitPos
	local Ang = trace.HitNormal:Angle()

	if ( !self:GetSWEP():CheckLimit( "repulsors" ) ) then return false end

	
	local keyup 			= self:GetClientNumber( 'keyup' ) 
	local keydown 			= self:GetClientNumber( 'keydown' ) 
	local rate 				= self:GetClientNumber( 'rate' )
	local damping			= self:GetClientNumber( 'damping' )
	local strength			= self:GetClientNumber( 'strength' )
	local downonly 			= self:GetClientNumber( 'downonly' )
	local mask = 0
	local distance			= self:GetClientNumber( 'distance' )

	for name, value in pairs(masks) do
		mask = bit_or(mask,(value * self:GetClientNumber( name )))
	end

	Ang.pitch = Ang.pitch + 90

	if self:GetClientNumber('forceinitial') == 1 then
		distance = 8192
		local dtrace = {}
			dtrace.start = trace.HitPos
			dtrace.mask = mask
		if downonly == 1 then
			dtrace.endpos = dtrace.start - Vector(0,0,distance)
		else
			--dtrace.endpos = dtrace.start + (Ang:Normalize() * distance)
			dtrace.endpos = dtrace.start - Vector(0,0,distance)

		end
		tr = util.TraceLine(dtrace)
		distance = tr.Fraction * distance
		print("Distance set to " .. distance)
	end
	
	ent = MakeRepulsor( ply, Ang, Pos, keyup, keydown, rate, strength, damping, downonly, mask, distance )

	ent:SetPos( trace.HitPos - trace.HitNormal * ent:OBBMins().z)
	
	if ( trace.Entity:IsValid() ) then
		local const = constraint.Weld(ent, trace.Entity,0, trace.PhysicsBone, 0, systemmanager )
		--nocollide = constraint.NoCollide( ent, trace.Entity, 0, trace.PhysicsBone )
		trace.Entity:DeleteOnRemove( ent )
	end

	undo.Create('Repulsor')
		undo.AddEntity( ent)
		undo.AddEntity( const )
		--undo.AddEntity( nocollide )
		undo.SetPlayer( ply )
	undo.Finish()


	ply:AddCleanup( 'repulsors', thruster )
	ply:AddCleanup( 'repulsors', const )
	ply:AddCleanup( 'repulsors', nocollide )
	
	return true
end

if SERVER then 
    CreateConVar('sbox_maxrepulsors',10)
	
	function MakeRepulsor( ply, Ang, Pos, keyup, keydown, rate, strength, damping, downonly, mask, distance )
		if ( !ply:CheckLimit( "repulsors" ) ) then return nil end
		local ent = ents.Create( 'malawar_repulsor' )
		if !ent:IsValid() then return false end

		model = 'models/props_c17/clock01.mdl'
	
		ent:SetModel(model)
		
		ent:SetAngles(Ang)
		ent:SetPos(Pos)
		ent:Spawn()
		--ent:SetColor(0,0,0,255)
		
		ent:SetMaterial(repulsor_material) 
		
		ent:SetNetworkedInt('DownOnly',downonly)
		
		numpad.OnDown(ply, keyup, 'Repulsor_On', ent, 1)
		numpad.OnUp(ply, keyup, 'Repulsor_Off', ent, 1)

		numpad.OnDown(ply, keydown, 'Repulsor_On', ent, -1)
		numpad.OnUp(ply, keydown, 'Repulsor_Off', ent, -1)

		local rtable = {
			keyup	= keyup,
			keydown = keydown,
			rate	= rate,
			damping = damping,
			strength = strength,
			mask = mask,
			downonly = downonly,
			distance = distance
			}

		table.Merge(ent:GetTable(), rtable )
		
		ply:AddCount('repulsors', ent)
		
		return ent
	end
	
	duplicator.RegisterEntityClass( 'malawar_repulsor', MakeRepulsor,  'Ang', 'Pos', 'keyup', 'keydown', 'rate', 'damping', 'strength' )
	
end

function TOOL.BuildCPanel( cp )
	cp:AddControl( 'Header', { Text = '#Tool_repulsor_name', Description	= '#Tool_repulsor_desc' }  )

	cp:AddControl( 'Slider', { Label = 'Strength', Type = "Float", Min = 1, Max = 10000, Command = 'repulsor_strength' } )
	cp:AddControl( 'Slider', { Label = 'Damping', Type = "Float", Min = 1, Max = 1000, Command = 'repulsor_damping' } )
	cp:AddControl( 'Slider', { Label = 'Initial Distance', Type = "Float", Min = 1, Max = 1000, Command = 'repulsor_distance' } )
	cp:AddControl( 'Slider', { Label = 'Rate of Distance Change', Type = "Float", Min = 1, Max = 1000, Command = 'repulsor_rate' } )
	cp:AddControl( "Checkbox", { Label = "Only repulse straight down", Command = "repulsor_downonly" } )
	cp:AddControl( "Checkbox", { Label = "Force initial distance to starting height", Command = "repulsor_forceinitial" } )

	cp:AddControl(  "Label", { Text = "Types of items to repulse against:" } )

	for name, value in pairs(masks) do
		cp:AddControl("Checkbox", { Label = name, Command = "repulsor_" .. name } )
	end

	cp:AddControl('Numpad', { Label = 'Increase Distance:', ButtonSize = '22', Command = 'repulsor_keyup',Label2 = 'Decrease Distance:', Command2 = 'repulsor_keydown' } )
	
end

function TOOL:UpdateGhostRepulsor( ent, player )

	if ( !ent ) then return end
	if ( !ent:IsValid() ) then return end

	local tr 	= utilx.GetPlayerTrace( player, player:GetCursorAimVector() )
	local trace 	= util.TraceLine( tr )
	if (!trace.Hit) then return end
	
	if (trace.Entity && trace.Entity:GetClass() == "malawar_repulsor" || trace.Entity:IsPlayer() || trace.HitWorld || trace.Entity:IsNPC()) then
	
		ent:SetNoDraw( true )
		return
		
	end
	
	local Ang = trace.HitNormal:Angle()
	Ang.pitch = Ang.pitch + 90
	ent:SetAngles( Ang )
	
	local min = ent:OBBMins()
	 ent:SetPos( trace.HitPos - trace.HitNormal * min.z )
	ent:SetMaterial(repulsor_material) 
	
	ent:SetNoDraw( false )
	
end


function TOOL:Think()
	local model
	
	model = 'models/props_c17/clock01.mdl'

	if (!self.GhostEntity || !self.GhostEntity:IsValid() || self.GhostEntity:GetModel() != model) then
		self:MakeGhostEntity( model, Vector(0,0,0), Angle(0,0,0) )
	end
	
	self:UpdateGhostRepulsor( self.GhostEntity, self:GetOwner() )
	
end
